#ifndef __BB_BASE_NODE_H__
#define __BB_BASE_NODE_H__

#include <algorithm>
#include <any>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <variant>
#include <vector>

#include "../exception/exception.h"
#include "../processor/processor.h"
#include "data_stream/data_handle.h"
#include "data_stream/data_stream.h"
#include "node_info.h"

namespace bb::node {

class BaseNode {
  protected:
    bool                                     initialized_{false};
    std::string                              name_;
    std::shared_ptr<BaseNode>                father_;
    std::map<int, std::shared_ptr<BaseNode>> childs_;
    std::shared_ptr<data::DataHandle>        data_handle_;
    NodeStatus                               status_ = NodeStatus::Idle;

    std::unordered_map<std::string, std::shared_ptr<processor::Processor>> scripts_;

  public:
    virtual auto Type() -> NodeType       = 0;
    virtual auto TypeStr() -> std::string = 0;

  public:
    /* constructor */
    BaseNode(std::string name) : name_{name} {}

  public:
    /* script */
    virtual void Init()                       = 0;
    virtual void Reset()                      = 0;
    virtual void TickSubNodes()               = 0;
    virtual auto UpdateStatus() -> NodeStatus = 0;

#ifndef CallScript
#define CallScript(name)                             \
    if (scripts_.count("name"))                      \
        (void)scripts_.at("name")->Call();           \
    else if (#name == "Ready" or #name == "Process") \
        throw exception::LogicError("Node [", Name(), "] has no script called: ", #name);

    virtual auto ProcessOnce() -> NodeStatus {
        InitAllScript();
        CallScript(OnEnter); // 进入节点时无条件执行
        CallScript(Ready);
        if (not scripts_.at("Ready")->OutputValue("is ready")->ToBool()) { // 节点是否准备好
            CallScript(IfNotReady);                                        // 若节点未Ready，后于Ready()
            SeriouslyResetStatus();                                        // back to Idle.
            return Status();
        }
        InitSubNodes();            // 初始化子节点
        CallScript(IfIsReady);     // 若节点Ready，后于Ready()
        CallScript(BeforeProcess); // 若节点Ready，在Process前执行
        CallScript(Process);       // 主Process函数
        TickSubNodes();            // tick子节点，由具体类实现（不同类型节点tick规则不同）
        CallScript(AfterProcess);  // 在Process后无条件执行
        SetStatus(UpdateStatus()); // 更新自身Status，由具体类实现（不同类型节点update规则不同）
        if (Status() == NodeStatus::Running) CallScript(IfRunning); // 在节点Running后执行，后于AfterProcess()
        if (Status() == NodeStatus::Success) CallScript(IfSuccess); // 在节点Success后执行，后于AfterProcess()
        if (Status() == NodeStatus::Failed) CallScript(IfFailed);   // 在节点Failed后执行，后于AfterProcess()
        CallScript(OnExit);                                         // 节点退出时无条件执行
        if (Status() == NodeStatus::Success or Status() == NodeStatus::Failed) ResetSubNodes(); // 重置所有子节点
        ResetAllScript();
        return Status();
    }
#undef CallScript
#endif

  public:
    /* process */
    void InitSubNodes() {
        for (auto [sub_name, sub_node] : childs_) sub_node->Init();
    }
    void ResetSubNodes() {
        if (Status() == NodeStatus::Success or Status() == NodeStatus::Failed)
            for (auto [sub_name, sub_node] : childs_) sub_node->Reset();
    }

  public:
    /* set */
    void SetFather(std::shared_ptr<BaseNode> father) {
        father_      = father;
        initialized_ = true;
    }
    void AddChild(std::shared_ptr<BaseNode> child) { childs_.insert_or_assign(childs_.size(), child); }
    void SetDataHandle(std::shared_ptr<data::DataHandle> data_handle) { data_handle_ = data_handle; }

    void ResetDataStream() {
        data_handle_->ResetDataInputs();
        data_handle_->ResetDataOutputs();
        data_handle_->ResetDataVariables();
    }

    void SeriouslyResetStatus() { status_ = NodeStatus::Idle; }

    void ResetStatus() {
        status_ = NodeStatus::Idle;
        std::cout << "Node [" << Name()
                  << "]: reset status to [Idle] manually is not recommended!"
                     "use SeriouslyResetStatus() instead if you don't like this tip!"
                  << std::endl;
    }

    void SetStatus(NodeStatus status) {
        if (status != NodeStatus::Idle)
            status_ = status;
        else
            throw exception::RuntimeError("Node [", Name(),
                                          "]: set status to [Idle] manually is not allowed!"
                                          "use ResetStatus() instead if you really want to do so!");
    }

    void SetScript(std::string name, std::shared_ptr<processor::Processor> processor) { scripts_.insert_or_assign(name, processor); }
    void InitAllScript() {
        for (auto [_, script] : scripts_) script->Init(data_handle_);
    }
    void ResetAllScript() {
        for (auto [_, script] : scripts_) script->Reset();
    }

  public:
    /* get */
    auto Name() -> std::string { return name_; }
    auto FatherName() -> std::string { return father_->Name(); }
    auto Father() -> std::shared_ptr<BaseNode> { return father_; };
    auto Childs() -> std::map<int, std::shared_ptr<BaseNode>> const { return childs_; }
    auto Child(std::string name) -> std::shared_ptr<BaseNode> {
        auto iter = std::find_if(childs_.begin(), childs_.end(),
                                 [&name](std::pair<int, std::shared_ptr<BaseNode>> child) { return child.second->Name() == name; });
        if (iter != childs_.end())
            return iter->second;
        else
            throw exception::RuntimeError("Node [", Name(), "]: no such child node named ", name);
    }
    auto ChildNames() -> std::vector<std::string> {
        auto result = std::vector<std::string>{};
        for (auto [_, child] : childs_) result.emplace_back(child->Name());
        return result;
    }
    auto Status() -> NodeStatus { return status_; }
    auto DataHandle() -> std::shared_ptr<data::DataHandle> { return data_handle_; }
};

} // namespace bb::node

#endif // __BB_BASE_NODE_H__